<?php
/**
 * A collection of a single model class instances, stored in database.
 * By default, all stored instances belongs to the collection.
 * Acts as a lazy queryset. Implements Iterator interface.
 * @link http://www.martinfowler.com/eaaCatalog/repository.html
 *
 * Uses standard MySQL library actually
 * @todo implement iterator http://fr.php.net/manual/fr/language.oop5.iterations.php
 * @link http://webappkit.net
 * @package webappkit
 * @subpackage db
 * @author J.Ducastel <nospam0@ducastel.name>
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 */
class DbCollection implements iDbCollection {

    /**
     * constructor
     * @param DbLinkTool $link
     */
    public function __construct(&$link) {
        $this->link = $link;

        $this->query = new DbQuery();
        $this->query->setCount();
        $this->_initQuery();
    }

    // queryset / filtering methods

    /**
     * register a model-based filter
     * checks filter's validity and converts its value for sql use
     * search for self string _filter{$key}($value) method
     * @param string $key name of the model property to filter on
     * @param mixed $value value to filter on
     * @return bool true if filter is supported and value is correct
     */
    public function filter($key, $value) {
        $method = '_filter'.$key;
        if (method_exists($this,$method)) {
            // there is a custom validation / conversion method
            return $this->$method($value);
        } else {
            // filter is not supported
            throw new Exception("filter $key is not supported by this collection");
            return false;
        }
    }

    /**
     * remove model-based, named filter
     * @access public
     * @param string $key name of the model property to filter on
     * @return bool
     */
    public function removeFilter($key) {
        unset($this->filters[$key]);
        return true;
    }

    /**
     * chains filters grouped in array
     * @param array $filters associative array
     * @return DbCollection filtered instance
     */
    public function filters($filters) {
        foreach ($filters as $key => $value) {
            $this->filter($key, $value);
        }
        return $this;
    }

    /**
     * return rows count in current selection
     * @return int
     */
    public function count() {
        $sql = $this->query->getSql('count'); // echo $sql;
        $result = $this->link->getResult($sql);
        $line = $result->fetchAssoc(); // print_r($line);
        return intval($line['count']);
    }

    /**
     * get all items as an array
     * @access public
     * @return array|false list of objects
     */
    public function getList() {
        $list = array();
        if (!$result = $this->_getResult())
            return $list;
        while ($item = $this->_fetchItem($result)) {
            $list [] = $item;
        }
        return $list;
    }

    /**
     * gets a page of items
     * @param int $index (starting at 1)
     * @param int $lines lines per page, default 20
     * @return array(object)
     */
    public function getPage($index=1, $lines=20) {

    }

    /**
     * return pages count
     * @param int $lines lines per page
     * @return int
     */
    public function countPages($lines=20) {
        return ceil($this->count() / $lines);
    }

    /**
     * deletes the current selection. BE CAREFUL !
     * @return int items deleted count
     */
    public function deleteAll() {

    }/**/

    // insert / update / delete methods

    /**
     * get an item, either the first in selection or by primary key
     * @param mixed $key primary key value.
     * if null, will return the first item in selection
     * @return object
     */
    public function get($key=null) {
        // adding the key filter
        if ($key) {
            // flushing result
            $this->result = null;
            if(!$this->filter($this->filter_key, $key))
                return false;
        }
        // return the first object
        $result = $this->_getResult();
        return $this->_fetchItem($result);
    }

    /**
     * inserts a single item into the collection
     * if objects implements iDbCollectionItem, sets generated id
     * @param object $item
     * @return bool
     */
    public function insert(&$item) {
        // set insertion SQL
        if (!$sql = $this->_sqlInsert($item)
        or !$this->link->executeSql($sql)) {
            trigger_error($sql);
            return false;
        }
        return $this->_setItemId($item, $this->link->insertId());
    }/**/

    /**
     * updates a single item into the collection
     * @access public
     * @param object $item instance to save
     * @return bool
     */
    public function update(&$item) {
        // get update queries
        if (!$queries = $this->_updateQueries($item)) {
            return false;
        }
        // execute update queries
        $results = $this->link->getResults($queries, $this->use_transactions);
        foreach ($results as $result) {
            if ($result->failed)
                return false;
        }
        return true;
    }

    /**
     * deletes a single item
     * @param object $item object instance to delete
     * @return bool
     */
    public function remove($item) {

    }/**/

    /**
     * deletes a single item by key
     * @param scalar $id
     * /
    function deleteByKey($id) {
        $sql = $this->_buildDeleteByKey($id);
        return $this->_executeSql($sql);
    }/**/

    // iterator methods

    /**
     * return current (first ) item in collection
     * @return object
     */
    public function current() {
        if (!$this->current) {
            $result = $this->_getResult();
            $this->current = $this->_fetchItem($result);
        }
        return $this->current;
    }/**/

    /**
     * return key of the current element (or 0)
     * @return scalar
     */
    public function key() {

    }

    /**
     * set cursor to next element
     * @return bool
     */
    public function next() {

    }/**/

    /**
     * tells if there is a next element in collection
     * @return bool
     */
    public function valid() {

    }

    /**
     * resets cursor to first element
     * @return bool
     */
    public function rewind() {

    }/**/

    // DEFINITION PROPERTIES AND METHODS

    /**
     * @var string stored objects class
     * @access protected
     */
    protected $items_class;

    /**
     * @var string stored items

    /**
     * @var string main table
     */
    //protected $table;

    /**
     * @var string primary key filter
     * so get($key) will use _filter{$filter_key}()
     */
    protected $filter_key = 'id';

    /**
     * @var string class id/ primary key property
     */
    protected $items_key = 'id';

    /**
     * @var array(name => sql value)
     */
    // protected $filters=array();

    /**
     * @var DbLinkTool
     */
    protected $link;

    /**
     * @var DbResult
     */
    protected $result;

    /**
     * @var bool use transaction, only for InnoDb tables
     */
    protected $use_transactions = false;

    /**
     * @var DbQuery
     */
    protected $query;

    public $sql;

    public $error;

    /**
     * @var object current item
     */
    //var $current;

    /**
     * @var array items already retrieved
     */
    // var $items=array();

    /**
     * @var DbMapper reference to owner DbMapper
     */
    //var $mapper;

    // protected methods : instance generation etc

    /**
     * initialize the query object
     * child classes have to implement this method
     * @return bool
     */
    protected function _initQuery() {
        return false;
    }

    /**
     * builds the update queries fro the item
     * @param Object $item
     * @return array queries to execute
     */
    protected function _updateQueries($item) {
        return array();
    }

    // UTILITY METHODS

    /**
     * gets the select mysqli_result
     * @return mysqli_result
     */
    protected function & _getResult() {
        if (!$this->result) {
            $sql = $this->query->getSql('select');
            $this->result = $this->link->getResult($sql);
            $this->error = $this->link->getError();
            $this->sql = $sql;
        }
        return $this->result;
    }

    /**
     * builds a collection's item instance from current result
     * @param DbResult $result
     * @return object|false
     */
    protected function _fetchItem(&$result) {
        if(!$data = $result->fetchAssoc())
            return false;
        $class = $this->items_class;
        return new $class($data);
    }

    /**
     * sets item id
     * @param object $item
     * @param string $id
     * /
    function _setItemId($item, $id) {
        $item->id = $id;
        return true;
    }/**/

    /**
     * returns primary key filter from item
     */


    // utilities

    /**
     * protects and converts value for query insertion
     * @param mixed $value source
     * @param bool $enclose if true, adds ' around value
     * @return string
     */
    protected function _sqlValue($value, $enclose=false) {
        switch (true) {
            case is_bool($value):
                $value = $value?'1':'0';
                break;
            case is_int($value):
                $value=strval($value);
                break;
            case is_a($value, 'DateTimeBean'):
                $value = $value->toMysqlDateTime();
                break;
        }
        $value = mysql_escape_string($value);
        if ($enclose)
            $value = '\''.$value.'\'';
        return $value;
    }

    /**
     * sets item id / primary key
     * @param Object $item
     * @param string $id
     * @return bool
     */
    protected function _setItemId(&$item, $id) {
        $key = $this->items_key;
        $item->$key = $id;
        return true;
    }

    /**
     * executes a query
     * @access protected
     * @param string $sql SQL query
     * @return resource
     * /
    function _executeSql($sql) {
        return $this->link->executeSql($sql);
    }/**/

    /**
     * checks that is an object of the correct class
     * @access protected
     * @param mixed $object
     * @return bool
     */
    function _isObject($object) {
        return is_a($object, $this->class);
    }
}